// Bristleback plugin - Copyright (c) 2010 bristleback.googlecode.com
// ---------------------------------------------------------------------------
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
// This library is distributed in the hope that it will be useful,
// but without any warranty; without even the implied warranty of merchantability
// or fitness for a particular purpose.
// You should have received a copy of the GNU Lesser General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/lgpl.html>.
// ---------------------------------------------------------------------------
package pl.bristleback.server.bristle.actions;

import org.apache.log4j.Logger;
import org.jwebsocket.api.WebSocketConnector;
import org.jwebsocket.token.Token;
import pl.bristleback.server.bristle.config.BristleConstants;
import pl.bristleback.server.bristle.exceptions.RemoteActionNotFoundException;
import pl.bristleback.server.bristle.exceptions.TokenElementMissingException;
import pl.bristleback.server.bristle.exceptions.handlers.ActionExceptionType;
import pl.bristleback.server.bristle.exceptions.handlers.ExceptionHandlersContainer;
import pl.bristleback.server.bristle.rights.ConnectorRightsUtil;

/**
 * This class maintains incoming messages and executes action classes based on information taken from message.
 * It uses token's 'type' variable to indicate which action should be executed.
 * If requested action class is not set to be a singleton, it creates new instance of action class
 * (with dependencies and other fields injected).
 * Even if the action with name given in message is found, dispatcher checks rights of the connector,
 * so programmer can control whether connector is able to call this action.
 * Any exception can be caught by {@link pl.bristleback.server.bristle.exceptions.handlers.ExceptionHandler} implementations.
 * For more information about handling action exception, please refer to action exception handler documentation.
 * <p/>
 * Created on: 2010-09-03 16:02:22 <br/>
 *
 * @author Wojciech Niemiec
 */
public final class ActionsDispatcher {
  private static Logger log = Logger.getLogger(ActionsDispatcher.class.getName());

  private ActionsContainer container;
  private ExceptionHandlersContainer exceptionHandlersContainer;
  private ActionInstanceCreator actionInstanceCreator;

  public ActionsDispatcher() {
    container = new ActionsContainer();
    actionInstanceCreator = new ActionInstanceCreator();
  }

  /**
   * Performs action with name given in token content.
   *
   * @param connector connector.
   * @param token     message content and additional information.
   */
  public void performAction(WebSocketConnector connector, Token token) {
    try {
      RemoteActionInformation actionInformation = getActionInformation(token);
      invokeAction(actionInformation, connector, token);
    } catch (TokenElementMissingException e) {
      exceptionHandlersContainer.invokeHandler(ActionExceptionType.ACTION_NAME_NOT_SPECIFIED, connector, token);
    } catch (RemoteActionNotFoundException e) {
      exceptionHandlersContainer.invokeHandler(ActionExceptionType.ACTION_NOT_FOUND, connector, token);
    }
  }

  private RemoteActionInformation getActionInformation(Token token) {
    String actionName = getActionName(token);
    RemoteActionInformation actionInformation = container.getAction(actionName);
    if (actionInformation == null) {
      throw new RemoteActionNotFoundException();
    }
    return actionInformation;
  }

  private String getActionName(Token token) {
    String actionName = token.getType();
    if (actionName == null) {
      throw new TokenElementMissingException(BristleConstants.ACTION_NAME_TOKEN_VARIABLE);
    }
    return actionName;
  }

  private void invokeAction(RemoteActionInformation actionInformation, WebSocketConnector connector, Token token) {
    if (ConnectorRightsUtil.hasRights(connector, actionInformation.getRequiredRights())) {
      RemoteAction action = getAction(actionInformation);
      action.performAction(connector, token);
    } else {
      handleMissingRightsError(actionInformation, connector, token);
    }
  }

  /**
   * Gets an action class object, checking whether a new instance of action class should be created.
   *
   * @param actionInformation action class information.
   * @return action class instance, passed or created.
   */
  private RemoteAction getAction(RemoteActionInformation actionInformation) {
    if (actionInformation.isSingleton()) {
      return actionInformation.getAction();
    } else {
      return actionInstanceCreator.createInstance(actionInformation);
    }
  }

  private void handleMissingRightsError(RemoteActionInformation actionInformation, WebSocketConnector connector, Token token) {
    if (actionInformation.isActionHandlingErrors()) {
      actionInformation.handleMissingRightsError(connector, token);
    } else {
      exceptionHandlersContainer.invokeHandler(ActionExceptionType.RIGHTS_NOT_SUFFICIENT, connector, token);
    }
  }

  /**
   * Performs actions with names reserved by plugin.
   *
   * @param connector  websocket connector.
   * @param actionName name of the action to invoke.
   * @param token      content of the message.
   */
  public void performReservedAction(WebSocketConnector connector, ReservedActionName actionName, Token token) {
    RemoteActionInformation actionInformation = container.getAction(actionName.toString());
    if (actionInformation != null) {
      RemoteAction action = getAction(actionInformation);
      action.performAction(connector, token);
    }
  }

  /**
   * Gets actions container, containing action wrappers.
   *
   * @return actions container.
   */
  public ActionsContainer getContainer() {
    return container;
  }

  /**
   * Sets actions container. This method is invoked by plugin.
   *
   * @param container actions container.
   */
  public void setContainer(ActionsContainer container) {
    this.container = container;
  }

  /**
   * Sets exception handlers container. This method is invoked by plugin.
   *
   * @param exceptionHandlersContainer exception handlers container.
   */
  public void setExceptionHandlersContainer(ExceptionHandlersContainer exceptionHandlersContainer) {
    this.exceptionHandlersContainer = exceptionHandlersContainer;
  }
}

